Log In  
BBS > Lexaloffle Community Superblog
This is a combined feed of all Lexaloffle user blogs. For Lexaloffle-related news, see @zep's blog.

All | Following | PICO-8 | Voxatron | General | Off-site
[ :: Read More :: ]

As anyone who has posted a Pico-8 cartridge has noticed, there is a 10-character identification name to allow you to load the Pico-8 cartridge in immediate mode thus:

load #dofinihade

Where you type LOAD, pound sign, then the 10-character name followed by the ENTER key.

However something I noticed rather early and surprising is that the codes repeat vowels and consonants.

Here then is a more robust code generator that ensures no repeats, can produce 12-billion codes, yet limited to 32767 as this is the highest number for Pico-8.

Nonetheless it is complete. Generating a 10-letter code with no repeated letters per code and checks to make sure it is not a code already generated for a previous ID number.

As there are 10-letters each, every code always uses every vowel.

If you discount duplicates there are a total of 12,762,815,625 possible codes to be generated from this format.

Cart #huwihewesa-0 | 2021-11-12 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA

P#99996 2021-11-12 18:53 ( Edited 2021-11-12 18:56)
[ :: Read More :: ]

Cart #petcapdog-0 | 2021-11-12 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA
10


best petting simulator 2021 @ some reviewer idk

P#99973 2021-11-12 12:46
[ :: Read More :: ]

Cart #dofinihade-0 | 2021-11-11 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA
1


TO LOAD THIS PICO-8 cart:
In immediate mode type:

load #dofinihade

Since a great many suggestions from users including myself have come to pass for @zep to incorporate in future Pico-8, I would like to suggest a few more if I may.

Three new commands: GET, PUT, and WAIT

Also grid for image edit. You already have a grid for mapper but not for sprites. Could be activated and deactivated while in sprite edit mode with CTRL+G.

GET and PUT are familiar to QBasic amongst other programming languages and allow you to transfer image contents to and from arrays, strings, and string arrays.

For Pico-8 the GET function would work thus:

PGETP(S,X,Y,W,H)

This would grab from the visible display screen all the pixels starting at screen coordinates X+Y and have a width and height of W and H, to be placed 2-pixels per byte directly into string S also saving the size of the image recorded.

They could be recalled thus via PUT:

PPUTP(S,X,Y,{W,H})

Which would draw string S at screen coordinates X+Y and have an optional Width and Height adjust for the PUT picture, useful for mirror X, mirror Y and for scaling.

This could also be done for sprites thus:

SGETP(S,X,Y,W,H)

and

SPUTP(S,X,Y,{W,H})

While I realize you can draw directly to the screen with sprites, this method would allow you to directly modify the screen and sprite table via GET and PUT images converted to/from standard string variables, useful for instance if you wanted to make a copy of the screen or in a string array to make an animated sequence that would be too slow and extraneous typing in a FOR/NEXT loop of drawing/saving pixels.

Also invaluable for tweets as you could include drawing sprites directly in the source and not at all need the sprite mapper area.

The third command is simple yet useful also for tweets to wait for a keystroke.

K=KEYWAIT({D,R})

The optional arguments are for the duration of button repeat and how fast the repeats are made. See BTNP() in the online instructions for Pico-8 for details. If D and/or R are not used, they are default speed.

https://www.lexaloffle.com/dl/docs/pico-8_manual.html#BTNP

The results from K are 0-5 to show button keypresses. Internally it uses BTNP(). With no operand it is:

KEYWAIT({D,R})

And merely waits for any keystroke and pauses until it receives one.

Here, I will write the function demos to show how they could be created, except it would be up to someone else to draw scaled.

-- convert image + string and
-- simple wait for keystroke
-- written by dw817 (11-11-21)

-- standard ⌂ pico-8 license

function _init()

-- speed up key repeat
poke(0x5f5c,8)
poke(0x5f5d,1)

cls()

rect(0,0,7,7)

-- grab this rectangle and put
-- in string s
s=pgetp(0,0,8,8)

?""
?""
?"area grabbed is 8x8"
?"string size is "..#s.." chars"
?""

keywait()

-- put string s on the screen,
-- just beside the drawn
-- square
pputp(s,9,0)

repeat
  k=keywait()
  ?"key="..k
until forever

end

function _update()
end

-- transfer image region to
-- string variable
function pgetp(x,y,w,h)
local c,f,t,n=chr,0,w*h,0
local s,p=c(w)..c(h)
  for i=0,h-1 do
    for j=0,w-1 do
      p=pget(x+j,y+i)
      if f==0 then
        n=n+p*16
      else
        n=n+p s=s..c(n) n=0
      end
      f=1-f t-=1
    end
  end
  if (f==1) s=s..c(n)
  return s
end

-- transfer string variable
-- to image + location
function pputp(s,x,y)
function o(a,b)
  return ord(sub(a,b,b))
end
local f,w,h,c=0,o(s,1),o(s,2),3
  for i=0,h-1 do
    for j=0,w-1 do
      if f==0 then
        pset(x+j,y+i,o(s,c)\16)
      else
        pset(x+j,y+i,o(s,c)%16)
        c+=1
      end
      f=1-f
    end
  end
end

-- wait for and return key by
-- id number
function keywait()
local r=-1
  repeat
    for i=0,5 do
      if (btnp(i)) r=i
    end
    flip()
  until r>=0
  return r
end
P#99951 2021-11-11 23:01 ( Edited 2021-11-11 23:04)
[ :: Read More :: ]

I'm working on my first original idea after having followed. A few tutorials. Going to be a 2 player cooperative falling block puzzle. I'm using Sprite flags to check the color of adjacent blocks. Each color with its own flag. But, is there a way I can check for ANY flag for the purpose of collision detection? Or do I need to set aside a separate flag or that?

P#99939 2021-11-11 17:55 ( Edited 2021-11-11 17:56)
[ :: Read More :: ]

Cart #fuyefumupi-0 | 2021-11-11 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA
1

P#99935 2021-11-11 12:37
[ :: Read More :: ]

Cart #redblocc-0 | 2021-11-10 | Code ▽ | Embed ▽ | No License
1


my first tweetcart ever :> the URL to it is right here

P#99921 2021-11-10 21:40
[ :: Read More :: ]

Cart #simple_timer_service-6 | 2021-11-19 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA
6

Simple Timers Service

Here is a Simple Timers Service that can manage multiple timed events -- feel free to use it in your games :D

How to use

This service is a Singleton and can be referenced with the variable "timer".

The service exposes 2 methods:

  • timer:start( _frames , _callback )
    • Simply start any number of timers, wherever you want in your code
    • _frames: the number of frames to count down before executing the callback function
    • _callback: the function to execute after the number of frames has elapsed
    • Example: "timer:start(600,my_function)" -- this will execute "my_function()" after 600 frames have elapsed
  • timer:update()
    • Simply place this into the pico _update or _update60 function to allow the service to update itself each frame
    • Note that update60 will cause the timer to "count" twice as fast, since it is counting down the number of frames
    • The service uses this internally to iterate all of the timers that have been started in order to decrement each / execute callbacks as necessary

The code

In the cart, Tab 0 is the Simple Timers Service code. Tab 1 is a commented example of how to use it in your project. I have pasted the code below for your convenience:

Tab 0 - Simple Timers Service

--simple timers service
--by buck young | professir
timer=(function()
 local sts={
  update=function(e) for i=1,#e.l do e.l[i].t-=1 if e.l[i].t<=0 then e.l[i].f();deli(e.l,i) return end end end,
  start=function(e,t,f) add(e.l, {t=t,f=f}) end,
 } 
 sts.__index=sts
 return setmetatable({l={}},sts)
end)()

Tab 1 - Example Implementation

-- example implementation
function _init()
 cls(0)

 -- start a timer
 -- from anywhere in your
 -- code. this code says
 -- "after 30 frames, call
 -- the 'green_screen'
 -- function":
 timer:start(30,green_screen)

 -- you can even in-line
 -- an anonymous function.
 -- this call says "after
 -- 60 frames, turn the 
 -- screen red":
 timer:start(60,function()
    cls(8)
 end)

 -- you can start as many 
 -- timers as you like,
 -- they are all managed
 -- within the service itself.
 timer:start(90,green_screen)

end

function _update()

 -- be sure to include this
 -- in your update function
 -- so the timers service
 -- can do its job:
 timer:update()

end

function green_screen()
 cls(11)
end

Many thanks to FReDs72 and merwok for some helpful discussions in the PICO-8 discord!

P#99913 2021-11-10 18:56 ( Edited 2021-11-23 05:01)
[ :: Read More :: ]

snäkätor - a demo released at Assembly Creative Tech 2021. Placed 2nd in the combined Wild / Short film compo.

Uses the tiniest trifill function by p01 from the Trifill Thunderdome, plus picotool for minification.

Seems to run quite well in browser with v0.2.3.

Cart #snakator-0 | 2021-11-10 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA
141

Well anyway, here's a video capture, too:

P#99910 2021-11-10 17:55
[ :: Read More :: ]

Cart #scientistwd_breakoutsky-0 | 2021-11-10 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA
11

Breakout Sky

A little something I made up from the LazyDevs tutorial. I wanted to try my hand at PICO-8, and I really had a lot of fun! Your job is to break up the clouds to reveal the sun again.

Control

Use "LEFT" and "RIGHT" to move, of course. Press "X" to kick, but be sure to do it with good timing! In addition, if you collect enough Stardust, you can hold down "O" to unleash a surprise!
Some blocks react differently if you kick the ball into them. Pay close attention!

Modes

STORY MODE is a set of thirteen levels. You get 9 lives to complete them, so good luck! It's the best mode to help you learn the game, too; I recommend beating it first.
ARCADE MODE is randomized from a set of twenty levels. You only have one life! Try to get as far as you can.

Credits

I want to thank the LazyDevs guy for making such a spot-on tutorial. I didn't follow it perfectly, and I even quit before getting to all of them. But without them, I might not have ever gotten to know PICO-8, and I thank him very much for all his hard work and his excellent attitude!
I've linked it here.

Behind the Scenes:

  • If you take the opportunity to look at the code, you'll see that I really could have done better with the particle effects, token-wise. I didn't really want to up the scope or graphical power of the game that much more anyway, but still. Yikes!
  • The first way I departed from the tutorial was the theme. I tried to give it a sky/rain theme, and add that bit of narrative to the game. Hence the dark clouds, lightning, and stardust.
  • The second departure was controls. I wanted to add the "kick" mechanic! I really like how it turned out, and I think it has a lot of potential to be more interesting. I felt that the powerups from the tutorial were a little too much to go with it, though.
  • I kinda wish I'd spent more time on the music, but I'm not confident at all in my abilities there! I usually make music for my games with Famitracker. PICO-8 has much less space! Even less than I thought! I hope the sound effects are good enough instead.
  • Likewise, my art isn't super. I'm not that messed up about it, though, just because I like how a lot of the other graphics turned out. Adding more detail to the ball or paddle felt a little bit wrong and distracting, even. And I had to leave the blocks simple to achieve the effect I wanted in the later levels of the story mode.
  • I had fun! I hope YOU have fun! Thanks for playing!!
P#99895 2021-11-10 02:31
[ :: Read More :: ]

How does the sin() function work in pico-8?
I've tried experimenting with it, but it doesn't seem to work the way I want it to.

cls()
print(sin(90)) -- returns 0

I really don't understand, why doesn't it return 1?

P#99863 2021-11-09 13:23
[ :: Read More :: ]

1: Direct print of stat(31) breaks printing

2: Using _update60 causes stat(30) to break/desync sometimes

3: Calling stat(31) breaks later stat(30)/stat(31) calls


Keys only register if 2+ are pressed at once.

With _update60:

P#99836 2021-11-09 01:05 ( Edited 2021-11-09 01:06)
[ :: Read More :: ]

A little experiment to create a swarm of rats.

Press Z to add rats. Press X to pop rats.
Press arrows to move cheese.

[0x0]

Cart #rats748392402-0 | 2021-11-08 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA

[0x0]

P#99831 2021-11-08 21:48 ( Edited 2021-11-08 22:00)
[ :: Read More :: ]

Cart #yomireyoso-0 | 2021-11-08 | Code ▽ | Embed ▽ | No License
1


Turns out that PICO-8 might actually be easier to use than I initially thought.

P#99821 2021-11-08 20:01
[ :: Read More :: ]

In an effort to easily contain 64-different characters for use in a compression table, I am seeing that if I use PRINTH() to @CLIP - that it is forcing the clipped data to be uppercase only in Pico-8.

a=""
for i=33,98 do
  c=i
  if (c!=34 and c!=92) a=a..chr(c)
end
printh(a,"@clip")

The results saved to the clipboard are:

!#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`ab

yet when pasted in Pico-8, you get:

!#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`AB

Where the last 2-characters are converted to uppercase.

So pasting with CTRL-V in Pico-8 apparently converts the entire clip to uppercase.

I would like to recommend that if you copy something to the clipboard from Pico-8, you have the option of it pasting it entirely as it is supposed to be.

printh(a,"@iclip")

"i" for ignore case for instance.

P#99805 2021-11-08 19:09 ( Edited 2021-11-08 19:34)
[ :: Read More :: ]

I didn't know much about compression algorithms when I started looking into this stuff and, in truth, I still know very little. So this may all be old-hat to a lot of people but hopefully somebody finds it interesting.

What am I compressing and why do I want to compress it?

For the last month or so I've been playing around with Signed Distance Fields (SDFs) first by making some simple pictures with them and using them to handle collsiion detection and then using them as the basis of a procedural morphing animation.

I'm interested in SDFs for a few reasons: I'm not much of an artist but I do like math. If I can substitute math for art in certain situations, that potentially works to my benefit. Also, since SDFs can be used to handle collision detection and they can be updated on the fly with boolean-like operators—union, intersection and difference—they seem like they could be a good choice for modeling level geomoetry and, in particular, destructible level geometry. But mostly I just like playing with them.

In general, you create a function which returns the minimum distance from any point on screen to the surface of whatever object you're modeling and then use that distance to determine pixel colour, or detect collisions, or whatever. But calling functions, especially complex functions as SDFs tend to be, is really slow, especially if you're doing it for every single pixel on screen. To get the animations to actually animate at a reasonable speed I had to pre-calculate all the distances and store them in an array so distance checks became table look-ups. Much faster.

Creating those functions and generating those arrays required a fairly large number of tokens though. So I've been learning about compression algorithms to store those arrays directly and use a, hopefully, smaller number of tokens to decompress them.

To compress, or not to compress

Like most things, it's a trade-off: for a multi-cart system you can probably fit a decent amount of SDF data per cart; for single carts, it's almost certainly not worth it.

SDF data is big. Not as big as I had originally thought but still pretty big. Even though I was ultimately able to get quite good compression ratios we're still talking about thousands of characters worth of binary data per screen of data stored. With a fixed limit of 65535 characters, that adds up fast. In fact, as I'll discuss later, it actually adds up even faster than you'd think. Each compressed SDF only requires three tokens but saving all the tokens in the world doesn't do you any good if you don't have any characters left to use them.

Test data

I mostly used the SDFs from the animation linked above as my compression test data. Here's, sort of, what they look like as distance fields.

Left-to-right, top-to-bottom: Square, Repeated triangles, Repeated circles, Repeated squares, Star, Rotated Star, Line, and Palm tree.

It's worth noting, again, that I'm storing the actual distance data itself and not these images speciifically. The images just give a sense for how the distance fields change and how simple or complex they are. An advantage of working with distance fields in that you can use the same data in multiple ways. Here's a quick little cart which demonstates the idea:

Cart #geyukukaha-0 | 2021-11-08 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA
3

Press 'x' to cycle through the different options. It's the same data in all cases, just being rendered differently.

How big is an SDF anyway?

At first I thought I might have to store fractional values so I'd need 32 bits per pixel. But no. In reality, at least how I'm using them so far, I'm working with integer distances on a single screen. The farthest away something can possibly be on a 128x128 display is about 180 or so along the diagonal: 8 bits is plenty.

Eight bits is definitely an improvement over 32 but still, that's one byte of data per pixel or 16384 bytes per screen of SDF data. At that size, a direct encoding of four SDFs would bust the PICO-8 character limit. The animation linked above uses eight SDFs.

So that number, 16384 bytes, is the base/uncompressed size for all my test data.

Compression algorithms

I tried a variety of algorithms both individually and in combination. These are the main ones.

Run length encoding (RLE)

RLE compresses by replacing a run of identical distances with a single instance of that distance and a number representing how many times it occurs before changing.

It was my assumption that RLE would be a bad choice for SDFs because, although some have long runs of repeated distances, most distances change with every pixel. If your run length is always one then instead of storing one integer per pixel, you're storing two.

Even so, I figured I'd test my assumptions by actually trying it and, sure enough, RLE on its own makes distance data larger, not smaller.

Huffman Coding

A Huffman coding encodes each unique distance with a different binary representation. Not all distances are represented with the same number of bits and the encoding is built in such a way that values which occur often use fewer bits than values which occur more rarely.

On its own, Huffman coding gave similar levels of compression as the LZW algorithm below.

Lempel-Ziv-Welch (LZW) compression

LZW is sort of, but not really, similar to RLE. It doesn't look for runs of identical distances but instead looks for sequences which it has seen before. When it finds one it inserts a reference to that sequence, essentially saying, "take that thing over there and put it over here as well."

Vector Distance Transform (VDT)

Once I thought to search for distance field specific compression algorithms, I found this paper describing VDT and it's the basis for the approach I decided to take so I'll describe it in a little more detail.

Rather than assigning a distance to each pixel, VDT assigns a vector to each pixel. The vector indicates which other (previously calculated) pixels, if any, can be used to calculate the distance for the current pixel. If we calculate pixels left-to-right and top-to-bottom then there are four possible vectors: the pixels directly above the current pixel, the pixels directly to the left of the current pixel, the pixels diagonally up and to the left of the current pixel, and the null vector indicating that the current distance can't be calculated based on previous pixels.

Since there are four possible vectors, each vector can be represented by two bits and the entire array of vectors takes up a total of 4096 bytes. Each null vector indicates a distance that we can't calculate and have to store directly, adding an additional 8 bits each, while every non-null vector is a distance that can be entirely eliminated from our data for a net savings of 6 bits each.

VDT on its own can reduce the size of an SDF fairly dramatically. But a nice feature of VDT is that, once the distance prediction step is taken, the vector data and remaining distance data can be further compressed using other methods. For instance, although RLE doesn't do so well with raw SDF data, it does a great job on the resulting vector data.

I tried two approaches and they gave very comparable results. Both start by doing the vector distance transformation.

The first approach then applied the RLE algorithm to the vector data and a Huffman coding to the distance data, finally combining the result into a single binary string.

The second approach starts by combining the vector and distance data into a single binary string and then running the LZW algorithm on that string to compress it further.

Below is a summary of the results I got via various methods. The VDT+LZW columns could just as well be VDT+RLE+Huffman since the results were very similar.

Lossless vs Lossy

I suspect that the palm tree SDF compresses so poorly because it contains a bunch of non-linear transformations: sines, cosines, exponentials, etc. which means the distance field isn't "well behaved" and, therefore, difficult to predict.

The VDT algorithm is lossless by default—it only removes a distance which can be predicted exactly—but is easily modified to be lossy. I wanted to see if I could get the palm tree SDF down to a more reasonable size without degrading the quality too badly. Spoiler alert: not really. It's easier to see when rendered as an image:

The first image is the lossless version as listed in the table above. The distortions in the second could be acceptable in some situations but still only gives a 74% compression ratio with a maximum squared error of 5. The last image, which looks like Thanos had a personal vendetta against trees, has a max squared error of 10 and still only compresses down to about 60% or a little under 10000 characters!

Compressing less to compress more

One particularly interesting discovery I made was that—even though the VDT+LZW combination gives the best compression on average—the best way to fit more SDFs per cart was to use VDT only. Why should that be the case? VDT by itself gives the worst compression of those listed. How is that better?

I figured this one out by accident when I copied the binary strings, except for the palm tree, into a cart, ran INFO and saw this:

The raw character count and the compressed character count are nearly the same. Which actually makes sense: I've compressed the data significantly so whatever algorithm PICO-8 is using to compress code isn't able to squeeze much more out of it. That particular screen shot is when using VDT+LZW but the same thing happens when using LZW only, Huffman coding only, and VDT+RLE+Huffman coding.

That got me thinking: PICO-8's compression is probably better than mine. So what if I only used VDT and let PICO-8 compress it the rest of the way for me? Here are the same seven SDFs with only VDT encoding.

It uses up a lot more of the raw character count but the compressed character count is much lower and there's still room to spare!

Edit: I forgot to actually post the compression/decompression code anywhere so here's a cart demonstrating it. The code is also on github

Cart #sdf_compression_demo-0 | 2021-11-10 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA
3

P#99798 2021-11-08 18:19 ( Edited 2021-11-10 11:22)
[ :: Read More :: ]

Cart #plorbo-3 | 2021-11-22 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA
7

An alien abduction game with procedurally generated planets

ATTENTION, PLORBO! We require new critters from across the Universe for our intergalactic zoo, the Pride of Plorbia. You must venture to new, unexplored worlds to find specific critters that we beam to your Mothership and return them to us.

Controls

When controlling green ship:

  • ARROW KEYS: move
  • Z: boost
  • X: abduct

When docked to Mothership:

  • DOWN: undock

When out of energy:

  • LEFT / RIGHT: control Mothership for a rescue

Any time:

  • Press ENTER to VIEW THE ZOO or MUTE THE MUSIC

​In the zoo:

  • Left/right to scroll
  • Hold Z + left/right to scroll quickly
  • X to exit zoo

How to Play

View the required head and body at the Mothership, then explore the planet to find a critter with both that head and body. Abduct it and return it to the Mothership. Do this a few times to visit the next planet. You can collect extra research points by finding critters with similar features, even if you do not find the exact match. You may abduct up to 5 creatures at once.

In between planetary visits, you will see the zoo with all the critters you have found so far. The zoo has some statistics listed - they will turn gold once you "complete" each stat.

Watch your energy meter (yellow meter above your craft). Moving, abducting, and boosting (especially) uses energy. Recharge at the Mothership. If you run out of energy and crash, you can control your Mothership to stage a rescue. Crash three times and thats game over!

External Links

Itch.io Game Page

P#99784 2021-11-08 15:46 ( Edited 2021-11-23 17:30)
[ :: Read More :: ]

Hey! I've been wanting to get into PICO-8, but I don't remember making an account with this email. Is there any way I can change my username? Thanks for any help.

P#99778 2021-11-08 04:20
[ :: Read More :: ]

Cart #rain-2 | 2021-11-07 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA
3

P#99763 2021-11-07 22:03
[ :: Read More :: ]

Cart #abbyboat-0 | 2021-11-07 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA
5

Made a new game with my daughter.

Pilot a Paddle Boat to the finish line.

Z - Left Paddle
X - Right Paddle

P#99762 2021-11-07 21:56
[ :: Read More :: ]

Cart #potassium-2 | 2021-11-08 | Code ▽ | Embed ▽ | No License
16


Kris Get The Banana
Original by Toby Fox

I tried and badly remixed Cool Mixtape and decided to make some visuals for it
This is my first time actually making music, so if anyone could give feedback it would be greatly appreciated.

P#99758 2021-11-07 21:24 ( Edited 2021-11-08 00:04)
View Older Posts